package com.googlecode.mailnet.emf.netmodel.simulation;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.googlecode.mailnet.emf.netmodel.DistributionModel;
import com.googlecode.mailnet.emf.netmodel.LinkModel;
import com.googlecode.mailnet.emf.netmodel.MessageFlowModel;
import com.googlecode.mailnet.emf.netmodel.NetworkConfigurationModel;
import com.googlecode.mailnet.emf.netmodel.NodeModel;
import com.googlecode.mailnet.emf.netmodel.QueueModel;
import com.googlecode.mailnet.emf.netmodel.QueueingConfigurationModel;
import com.googlecode.mailnet.math.distribution.*;
import com.googlecode.mailnet.netmodel.Link;
import com.googlecode.mailnet.netmodel.MessageFlow;
import com.googlecode.mailnet.netmodel.NetworkConfiguration;
import com.googlecode.mailnet.netmodel.Node;
import com.googlecode.mailnet.netmodel.QueueingConfiguration;

public class EMF2NetworkConfiguration {
	
	private NetworkConfiguration config = null;
	public final Map<NodeModel, Node> emf2nodes = new HashMap<NodeModel, Node>();
	public final Map<Node, NodeModel> nodes2emf = new HashMap<Node, NodeModel>();
	public final Map<LinkModel, Link> emf2links = new HashMap<LinkModel, Link>();
	public final Map<Link, LinkModel> links2emf = new HashMap<Link, LinkModel>();
	
	/**
	 * Converts EMF network configuration model to NetworkConfiguration object.
	 * @version 1.0
	 */
	NetworkConfiguration init(NetworkConfigurationModel model) throws DistributionFormatException, InvalidModelException {
		config = new NetworkConfiguration();
		emf2nodes.clear();
		emf2links.clear();
		nodes2emf.clear();
		links2emf.clear();
		
		// create nodes
		
		Iterator<NodeModel> it = model.getNodes().iterator();
		while (it.hasNext()) {
			NodeModel nodeModel = it.next();
			Node node = config.createNode();
			node.setFailureProbability(nodeModel.getFailureProbability());
			node.setName(nodeModel.getName());
			DistributionModel distributionModel = nodeModel.getFailureDurationDistribution();
			if (distributionModel == null) throw new InvalidModelException(nodeModel.getName(), "не указано распределение продолжительности отказов узла");
			node.setFailureDurationDistribution(DistributionParser.create( distributionModel.getValue()));
			emf2nodes.put(nodeModel, node);
			nodes2emf.put(node, nodeModel);
		}
		
		// create links
		
		Iterator<LinkModel> linkModelIterator = model.getLinks().iterator();
		while (linkModelIterator.hasNext()) {
			LinkModel linkModel = linkModelIterator.next();
			
			if (linkModel.getSource() == null) throw new InvalidModelException(linkModel.getName(), "не указан узел-источник канала связи");
			if (linkModel.getDestination() == null) throw new InvalidModelException(linkModel.getName(), "не указан узел-назначение канала связи");
			Link link = config.createLink(emf2nodes.get(linkModel.getSource()), emf2nodes.get(linkModel.getDestination()));
			link.setName(linkModel.getName());
			link.setDelay(linkModel.getDelay());
			link.setFailureProbability(linkModel.getFailureProbability());
			link.setPackageCorruptionProbability(linkModel.getPackageCorruptionProbability());
			
			DistributionModel distributionModel = linkModel.getFailureDurationDistribution();
			if (distributionModel == null) throw new InvalidModelException(linkModel.getName(), "не указано распределение продолжительности отказов канала связи");
			link.setFailureDurationDistribution(DistributionParser.create(distributionModel.getValue()));
			distributionModel = linkModel.getCapacityDistribution();
			if (distributionModel == null) throw new InvalidModelException(linkModel.getName(), "не указано распределение пропускной способности канала связи");
			link.setCapacityDistribution(DistributionParser.create(distributionModel.getValue()));
			
			emf2links.put(linkModel, link);
			links2emf.put(link, linkModel);
		}
		
		// create message flows
		Iterator<MessageFlowModel> messageFlowIterator = model.getMessageFlows().iterator();
		while (messageFlowIterator.hasNext()) {
			MessageFlowModel messageFlowModel = messageFlowIterator.next();
			
			MessageFlow messageFlow = config.createMessageFlow();
			
			messageFlow.setName(messageFlowModel.getName());
			messageFlow.setMessagesPriority(messageFlowModel.getMessagesPriority());
			
			DistributionModel distributionModel = messageFlowModel.getMessagesAmountDistribution();
			if (distributionModel == null) throw new InvalidModelException(messageFlowModel.getName(), "No message amount distribution");
			messageFlow.setMessagesAmountDistribution(DistributionParser.create(distributionModel.getValue()));
			distributionModel = messageFlowModel.getMessageSizeDistribution();
			if (distributionModel == null) throw new InvalidModelException(messageFlowModel.getName(), "No message size distribution");
			messageFlow.setMessageSizeDistribution(DistributionParser.create(distributionModel.getValue()));
			
			linkModelIterator = messageFlowModel.getRoute().iterator();
			Link[] links = new Link[messageFlowModel.getRoute().size()];
			int i = 0;
			// collect links in path and validate correct linking
			while (linkModelIterator.hasNext()) {
				LinkModel linkModel = linkModelIterator.next();
				links[i] = emf2links.get(linkModel);
				if (i != 0 && (links[i-1].getDestination() != links[i].getSource())) {
					// invalid linking
					throw new InvalidModelException(messageFlowModel.getName(), "Invalid route: links " + links[i-1].getName() + " and " + links[i].getName() + " are not ordered");
				}
				++i;
			}
			
			messageFlow.setRoute(links);
		}
		
		// set configuration
		QueueingConfigurationModel qcm = model.getQueueConfiguration();
		if (qcm != null) {
			if (qcm.getQueues().size() == 0) {
				throw new InvalidModelException("Queueing configuration", "No queues defined");
			}
			QueueingConfiguration qc = config.getQueueingConfiguration();
			qc.setQueuesNumber(qcm.getQueues().size());
			
			Iterator<QueueModel> q_it = qcm.getQueues().iterator();
			int i = 0;
			while (q_it.hasNext()) {
				QueueModel qm = q_it.next();
				if (qm.getWeight() < 0)
					throw new InvalidModelException("Queue model", "Invalid queue weight (" + qm.getWeight() + ")");
				qc.setQueueWeight(i, qm.getWeight());
				if (qm.getLimit() < 0)
					throw new InvalidModelException("Queue model", "Invalid queue limit (" + qm.getLimit() + ")");
				qc.setQueueLimit(i, qm.getLimit());
				qc.setStrictlyPrioritized(i, qm.isStrictlyPrioritized());
				++i;
			}
		}
		return config;
	}
}
